<?php


class PHPCodeGenerator
{
    public function __construct(PHPCodeProvider $provider)
    {
        if($provider->isInitialized()) throw new Exception(
            'The PHPCodeGenerator class is supposed to be used' . 
            'only by the PHPCodeProvider class.');
    }
    
    public function generateCodeFromCodeUnit(CodeUnit $codeUnit,
        IndentedTextWriter $writer, CodeGeneratorOptions $options)
    {
        $isStandalone = false;
        
        if($this->output != null && $this->output !== $writer)
        {
            throw new UnexpectedValueException(
                'Only allowed to write to Generator::output.');
        }
        
        if($this->output == null)
        {
            $isStandalone = true;
            $this->options = ($options == null) ? new CodeGeneratorOptions() : $options;
            $this->output = $writer;
        }
        
        $this->generateCodeUnit($codeUnit);
        
        if($isStandalone)
        {
            $this->output = null;
            $this->options = null;
        }
    }
    
    public function generateCodeFromExpression(Expression $expression,
        IndentedTextWriter $writer, CodeGeneratorOptions $options)
    {
        $isStandalone = false;
        
        if($this->output != null && $this->output != $writer)
        {
            throw new UnexpectedValueException(
                'Only allowed to write to Generator::output.');
        }
        
        if($this->output == null)
        {
            $isStandalone = true;
            $this->options = $options == null ? new CodeGeneratorOptions() : $options;
            $this->output = $writer;
        }
        
        $this->generateExpression($expression);
        
        if($isStandalone)
        {
            $this->output = null;
            $this->options = null;
        }
    }
    
    public function generateCodeFromNamespace(CodeNamespace $namespace,
        IndentedTextWriter $writer, CodeGeneratorOptions $options)
    {
        $this->options = ($options == null) ? new CodeGeneratorOptions() : $options;
        $this->output = ($writer == null) ? new IndentedTextWriter() : $writer;
        
        $this->generateNamespace($namespace);
    }
    
    public function generateCodeFromStatement(Statement $statement, 
        IndentedTextWriter $writer, CodeGeneratorOptions $options)
    {
        $isStandalone = false;
        
        if($this->output != null && $this->output !== $writer)
        {
            throw new UnexpectedValueException(
                'Only allowed to write to Generator::output.');
        }
        
        if($this->output == null)
        {
            $isStandalone = true;
            $this->options = $options == null ? new CodeGeneratorOptions : $options;
            $this->output = $writer;
        }
        
        $this->generateStatement($statement);
        
        if($isStandalone)
        {
            $this->output = null;
            $this->options = null;
        }
    }
    
    public function generateCodeFromType(TypeDeclaration $type, 
        IndentedTextWriter $writer, CodeGeneratorOptions $options)
    {
        $isStandalone = false;
        
        if($this->output != null && $this->output !== $writer)
        {
            throw new UnexpectedValueException(
                'Only allowed to write to Generator::output.');
        }
        
        if($this->output == null)
        {
            $isStandalone = true;
            $this->options = ($options == null) ? new CodeGeneratorOptions() : $options;
            $this->output = $writer;            
        }
        
        $this->generateType($type);
        
        if($isStandalone)
        {
            $this->output = null;
            $this->options = null;
        }
    }
    
    public function createEscapedIdentifier($name)
    {
        if($this->isValidIdentifier($name))
        {
            return $name;
        }
        elseif(is_numeric($name[0]))
        {
            $startingNumber = array($name[0]);
            
            for($i = 1; $i < strlen($name); $i++)
            {
                if(is_numeric($name[$i]))
                {
                    $startingNumber[] = $name[$i];
                }
                else
                {
                    break;
                }
            }
            
            if($this->isValidIdentifier(substr($name, count($startingNumber))))
            {            
                return $this->EscapeChar . $name;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }
    
    public function fileExtension()
    {
        return '.php';
    }
    
    public function get_Indent()
    {
        return $this->output->Indent;
    }
    
    public function set_Indent($value)
    {
        $this->output->Indent = $value;
    }
    
    public function isCurrentClass()
    {
        return ($this->currentClass != null) 
                    && ($this->currentClass->get_isClass());
    }
    
    public function isCurrentInterface()
    {
        return ($this->currentClass != null) 
                    && ($this->currentClass->get_isInterface());
    }
    
    public function isValidIdentifier($value)
    {
        if(!is_scalar($value))
        {
            return false;
        }
        
        if($value == null || strlen($value) == 0 || $value == '$this')
        {
            return false;
        }
        
        $pattern = '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#';
        
        if(preg_match($pattern, $value) == 0)
        {
            return false;
        }
        
        return true;
    }
    
    public function nullToken()
    {
        return 'null';
    }
    
    public function get_options()
    {
        return $this->options;
    }

        public function get_output()
    {
        return $this->output;
    }
    
    private function generateArgumentReferenceExpression(ArgumentReferenceExpression $expression)
    {
        $this->output->write('$');
        $this->outputIdentifier($expression->get_ParameterName());
    }
    
    private function generateArrayCreateExpression(ArrayCreateExpression $expression)
    {
        $this->output->write('array(');
        if($expression->Initializers->count() > 0)
        {
            $this->outputExpressionList($expression->Initializers, false);
        }
        $this->output->write(')');
    }
    
    private function generateArrayIndexerExpression(ArrayIndexerExpression $expression)
    {
        $this->generateExpression($expression->TargetObject);
        $this->output->write('[');
        $this->outputExpressionList($expression->Indices, false);
        $this->output->write(']');
    }
    
    private function generateAssignStatement(AssignStatement $statement)
    {
        $this->generateExpression($statement->get_Left());
        
        $statement->get_CompoundOperator() == null ?
            $this->output->write(' = ') :
            $this->output->write(' ' . $statement->get_CompoundOperator() . ' ');
        
        $this->generateExpression($statement->get_Right());
        if(!$this->generatingForLoop)
        {
            $this->output->writeLine(';');
        }
    }
    
    private function generateParentReferenceExpression(ParentReferenceExpression $expression)
    {
        $this->output->write('parent');
    }
    
    private function generateSelfReferenceExpression(SelfReferenceExpression $expression)
    {
        $this->output->write('self');
    }
    
    private function generateAttributes(CodeObject $object)
    {
        $attributes = $object->get_attributes();
        
        if($attributes->get_abstract())
        {
            $this->output->write('abstract ');
        }
        
        if($attributes instanceof ClassAttributes)
        {
            if($attributes->get_final())
            {
                $this->output->write('final ');
            }
        }
        
        if($attributes instanceof TypeMemberAttributes)
        {
            if($attributes->get_public())
            {
                $this->output->write('public ');
            }            

            if($attributes->get_private())
            {
                $this->output->write('private ');
            }            

            if($attributes->get_protected())
            {
                $this->output->write('protected ');
            }
            
            if($attributes->get_static())
            {
                $this->output->write('static ');
            }
        }

        if($attributes instanceof FieldAttributes)
        {
            if($attributes->get_const())
            {
                $this->output->write('const ');
            }
        }
    }
    
    private function generateBinaryOperatorExpression(BinaryOperatorExpression $expression)
    {
        $isNested = false;
        
        $this->generateExpression($expression->Left);
        $this->output->write(' ');
        
        if($expression->Left instanceof BinaryOperatorExpression 
            || $expression->Right instanceof BinaryOperatorExpression)
        {
            if(!$this->inNestedBinary)
            {
                $isNested = true;
                $this->inNestedBinary = true;
            }
            
            $this->continueOnNewLine('');
        }
        
        $this->outputBinaryOperator($expression->get_Operator());
        $this->output->write(' ');
        $this->generateExpression($expression->Right);
        
        if($isNested)
        {
            $this->inNestedBinary = false;
        }
    }
    
    private function generateCastExpression(CastExpression $expression)
    {
        $this->output->write('((');
        $this->output->write($expression->get_TargetType());
        $this->output->write(')(');
        $this->generateExpression($expression->Expression);
        $this->output->write('))');
        
    }
    
    private function generateComment(Comment $comment)
    {
        $text = explode(PHP_EOL, $comment->get_text());
        
        $this->output->writeLine('/*');
        foreach($text as $line)
        {
            $this->output->writeLine(' * ' . $line);
        }
        $this->output->writeLine(' */');
    }
    
    private function generateCommentStatement(CommentStatement $comment)
    {
        if($comment->get_comment() == null)
        {
            throw new InvalidArgumentException(
                'The comment can not be null.');
        }
        else
        {
            $this->generateComment($comment->get_comment());
        }
    }
    
    private function generateCommentStatements(CommentStatementCollection $comments)
    {
        foreach($comments as $comment)
        {
            $this->generateCommentStatement($comment);
        }
    }
    
    private function generateCodeUnit(CodeUnit $compileUnit)
    {
        $this->generateCodeUnitStart($compileUnit);
        $this->generateNamespaces($compileUnit);
        $this->generateCodeUnitEnd($compileUnit);
    }

    private function generateCodeUnitEnd(CodeUnit $compileUnit)
    {
        
        $this->output->writeLine();
        $this->output->writeLine('?>');
    }
    
    private function generateCodeUnitStart(CodeUnit $compileUnit)
    {
        $this->output->writeLine('<?php');
        $this->output->writeLine();
        
        foreach($compileUnit->Imports as $import)
        {
            $this->output->writeLine(
                $import->get_importType() . " '" . $import->get_fileName() . "';");
        }
        
        $this->output->writeLine();
        
    }
    
    private function generateConditionStatement(ConditionStatement $statement)
    {
        if($statement instanceof IfElseStatement)
        {
            $this->output->write('if(');
            $this->generateExpression($statement->get_Condition());
            $this->output->write(')');
            $this->outputStartingBrace();
            $this->output->Indent++;
            $this->generateStatements($statement->get_TrueStatements());
            $this->output->Indent--;
            
            if($statement->get_FalseStatements()->count() > 0)
            {
                $this->output->write('}');
                if($this->options->get_ElseOnClosing())
                {
                    $this->output->write(' ');
                }
                else
                {
                    $this->output->writeLine();
                }
                $this->output->write('else');
                $this->outputStartingBrace();
                $this->output->Indent++;
                $this->generateStatements($statement->get_FalseStatements());
                $this->output->Indent--;
            }
            
            $this->output->writeLine('}');
        }
        elseif($statement instanceof SwitchStatement)
        {
            $this->output->write('switch(');
            $this->generateExpression($statement->get_Condition());
            $this->output->write(')');
            $this->outputStartingBrace();
            $this->output->Indent++;
            
            foreach($statement->get_CaseStatements() as $caseStatement)
            {
                if(!$caseStatement->get_isDefault())
                {
                    $this->output->write('case (');
                    $this->generateExpression($caseStatement->get_CaseExpression());
                    $this->output->writeLine(') :');
                    $this->output->Indent++;
                    $this->generateStatements($caseStatement->get_Statements());
                    if($caseStatement->get_hasBreak())
                    {
                        $this->output->writeLine('break;');
                    }
                    $this->output->Indent--;
                }
            }
            
            foreach($statement->get_CaseStatements() as $caseStatement)
            {
                if($caseStatement->get_isDefault())
                {
                    $this->output->writeLine('default : ');
                    $this->output->Indent++;
                    $this->generateStatements($caseStatement->get_Statements());
                    $this->output->Indent--;
                    break;
                }
            }
            
            $this->output->Indent--;
            $this->output->writeLine('}');
        }
    }
    
    private function generateConstantReferenceExpression(ConstantReferenceExpression $expression)
    {
        $this->outputIdentifier($expression->get_ConstantName());
    }    
    
    private function generateDoubleQuotedStringExpression(
        DoubleQuotedStringExpression $expression)
    {
        $this->output->write('"');
        $this->output->write($expression->get_Value());
        $this->output->write('"');
    }
    
    private function generateEchoStatement(EchoStatement $statement)
    {
        $this->output->write('echo ');
        $this->outputExpressionList($statement->get_OutputParameters(), false);
        $this->output->writeLine(';');
    }
    
    private function generateExpression(Expression $expression)
    {
        if($expression instanceof PrimitiveExpression)
        {
            $this->generatePrimitiveExpression($expression);
        }
        elseif($expression instanceof ArrayCreateExpression)
        {
            $this->generateArrayCreateExpression($expression);
        }
        elseif($expression instanceof ParentReferenceExpression)
        {
            $this->generateParentReferenceExpression($expression);
        }
        elseif($expression instanceof BinaryOperatorExpression)
        {
            $this->generateBinaryOperatorExpression($expression);
        }
        elseif($expression instanceof CastExpression)
        {
            $this->generateCastExpression($expression);
        }
        elseif($expression instanceof FieldReferenceExpression)
        {
            $this->generateFieldReferenceExpression($expression);
        }
        elseif($expression instanceof ArgumentReferenceExpression)
        {
            $this->generateArgumentReferenceExpression($expression);
        }
        elseif($expression instanceof VariableReferenceExpression)
        {
            $this->generateVariableReferenceExpression($expression);
        }
        elseif($expression instanceof ArrayIndexerExpression)
        {
            $this->generateArrayIndexerExpression($expression);
        }
        elseif($expression instanceof SnippetExpression)
        {
            $this->generateSnippetExpression($expression);
        }
        elseif($expression instanceof MethodInvokeExpression)
        {
            $this->generateMethodInvokeExpression($expression);
        }
        elseif($expression instanceof FunctionInvokeExpression)
        {
            $this->generateFunctionInvokeExpression($expression);
        }
        elseif($expression instanceof MethodReferenceExpression)
        {
            $this->generateMethodReferenceExpression($expression);
        }
        elseif($expression instanceof FunctionReferenceExpression)
        {
            $this->generateFunctionReferenceExpression($expression);
        }
        elseif($expression instanceof ObjectCreateExpression)
        {
            $this->generateObjectCreateExpression($expression);
        }
        elseif($expression instanceof ParameterDeclarationExpression)
        {
            $this->generateParameterDeclaration($expression);
        }
        elseif($expression instanceof ThisReferenceExpression)
        {
            $this->generateThisExpression($expression);
        }
        elseif($expression instanceof SelfReferenceExpression)
        {
            $this->generateSelfReferenceExpression($expression);
        }
        elseif($expression instanceof ConstantReferenceExpression)
        {
            $this->generateConstantReferenceExpression($expression);
        }
        elseif($expression instanceof UnaryOperatorExpression)
        {
            $this->generateUnaryOperatorExpression($expression);
        }
        elseif($expression instanceof SingleQUotedStringExpression)
        {
            $this->generateSingleQuotedStringExpression($expression);
        }
        elseif($expression instanceof DoubleQuotedStringExpression)
        {
            $this->generateDoubleQuotedStringExpression($expression);
        }
        elseif($expression instanceof HereDocStringExpression)
        {
            $this->generateHereDocStringExpression($expression);
        }
        elseif($expression instanceof NowDocStringExpression)
        {
            $this->generateNowDocStringExpression($expression);
        }
    }
    
    private function generateExpressionStatement(ExpressionStatement $statement)
    {
        $this->generateExpression($statement->get_Expression());
        if(!$this->generatingForLoop)
        {
            $this->output->writeLine(';');
        }
    }
    
    private function generateField(Field $field)
    {
        $this->generateAttributes($field);
        
        if(!$field->get_attributes()->get_const())
        {
            $this->output->write('$');
        }
        $this->outputIdentifier($field->get_name());
        
        if($field->get_initExpression() != null)
        {
            $this->output->write(' = ');
            $this->generateExpression($field->get_initExpression());
        }
        
        $this->output->writeLine(';');
    }
    
    private function generateFieldReferenceExpression(FieldReferenceExpression $expression)
    {
        $this->generateExpression($expression->TargetObject);
        
        if($expression instanceof ParentReferenceExpression ||
            $expression instanceof SelfReferenceExpression)
        {
            $this->output->write('::$');
        }
        else
        {
            $this->output->write('->');
        }
        
        $this->output->write($expression->get_FieldName());
    }
    
    private function generateFields(TypeDeclaration $typeDeclaration)
    {
        foreach($typeDeclaration->Members as $member)
        {
            if($member instanceof Field)
            {
                if($this->options->get_BlankLinesBetweenMembers())
                {
                    $this->output->writeLine();
                }
                $this->generateCommentStatements($member->get_Comments());
                $this->generateField($member);
            }
        }
    }
    
    private function generateFunction(CodeFunction $function)
    {
        if($this->options->get_BlankLinesBetweenMembers())
        {
            $this->output->writeLine();
        }
        $this->generateCommentStatements($function->get_Comments());
        
        $this->output->write('function ');
        $this->outputIdentifier($function->get_Name());
        $this->outputParameters($function->get_Parameters());
        
        if($this->options->get_BracingStyle() == BracingStyle::KR)
        {
            $this->output->writeLine(' {');
        }
        elseif($this->options->get_BracingStyle() == BracingStyle::Allman)
        {
            $this->output->writeLine();
            $this->output->writeLine('{');
        }
        $this->output->Indent++;
        $this->generateStatements($function->get_Statements());
        $this->output->Indent--;
        $this->output->writeLine('}');
    }
    
    private function generateHereDocStringExpression(HereDocStringExpression $expression)
    {
        $this->output->writeLine('<<<' . $expression->get_Identifier());
        
        $indent = $this->output->Indent;
        $this->output->Indent = 0;
        
        $this->output->writeLine($expression->get_Value());
        $this->output->writeLine($expression->get_Identifier());
        
        $this->output->Indent = $indent;
    }
    
    private function generateIterationStatement(IterationStatement $statement)
    {
        if($statement instanceof ForStatement)
        {
            $this->generateForStatement($statement);
        }
        elseif($statement instanceof WhileStatement)
        {
            $this->generateWhileStatement($statement);
        }
        elseif($statement instanceof ForEachStatement)
        {
            $this->generateForEachStatement($statement);
        }
    }
    
    private function generateForStatement(ForStatement $statement)
    {
        $this->generatingForLoop = true;
        $this->output->write('for (');
        $this->generateStatement($statement->get_InitStatement());
        $this->output->write('; ');
        $this->generateExpression($statement->get_TestExpression());
        $this->output->write('; ');
        $this->generateStatement($statement->get_IncrementStatement());
        $this->output->write(') ');
        $this->outputStartingBrace();
        $this->generatingForLoop = false;
        $this->output->Indent++;
        $this->generateStatements($statement->get_Statements());
        $this->output->Indent--;
        $this->output->writeLine('}');
    }
    
    private function generateForEachStatement(ForEachStatement $statement)
    {
        $this->output->write('foreach (');
        $this->generateExpression($statement->get_Group());
        $this->output->write(' as ');
        $this->generateExpression($statement->get_Element());
        $this->output->write(') ');
        $this->outputStartingBrace();
        $this->output->Indent++;
        $this->generateStatements($statement->get_Statements());
        $this->output->Indent--;
        $this->output->writeLine('}');
    }
    
    private function generateWhileStatement(WhileStatement $statement)
    {
        if(!$statement->get_isDoWhile())
        {
            $this->output->write('while (');
            $this->generateExpression($statement->get_TestExpression());
            $this->output->write(') ');
        }
        else
        {
            $this->output->write('do ');
        }
        
        $this->outputStartingBrace();
        $this->output->Indent++;
        $this->generateStatements($statement->get_Statements());
        $this->output->Indent--;
        $this->output->write('} ');
        
        if($statement->get_isDoWhile())
        {
            $this->output->write('while (');
            $this->generateExpression($statement->get_TestExpression());
            $this->output->write(');');
        }
        
        $this->output->writeLine();
    }
    
    private function generateMethod(Method $method, TypeDeclaration $typeDeclaration)
    {
        $this->generateAttributes($method);
        $this->output->write('function ');
        $this->outputIdentifier($method->get_name());
        $this->outputParameters($method->Parameters);
        
        if($this->isCurrentClass())
        {
            if($this->options->get_BracingStyle() == BracingStyle::KR)
            {
                $this->output->writeLine(' {');
            }
            elseif($this->options->get_BracingStyle() == BracingStyle::Allman)
            {
                $this->output->writeLine();
                $this->output->writeLine('{');
            }
            $this->output->Indent++;
            $this->generateStatements($method->Statements);
            $this->output->Indent--;
            $this->output->writeLine('}');

        }
        elseif($this->isCurrentInterface())
        {
            $this->output->writeLine(';');
        }
    }
    
    private function generateFunctionInvokeExpression(FunctionInvokeExpression $expression)
    {
        $this->generateFunctionReferenceExpression($expression->get_Function());
        $this->output->write('(');
        $this->outputExpressionList($expression->get_Parameters(), false);
        $this->output->write(')');
    }
    
    private function generateMethodInvokeExpression(MethodInvokeExpression $expression)
    {
        $this->generateMethodReferenceExpression($expression->get_Method());
        $this->output->write('(');
        $this->outputExpressionList($expression->get_Parameters(), false);
        $this->output->write(')');
    }
    
    private function generateFunctionReferenceExpression(FunctionReferenceExpression $expression)
    {
        $this->outputIdentifier($expression->get_FunctionName());
    }
    
    private function generateMethodReferenceExpression(MethodReferenceExpression $expression)
    {
        if($expression->get_TargetObject() instanceof BinaryOperatorExpression)
        {
            $this->output->write('(');
            $this->generateExpression($expression->get_TargetObject());
            $this->output->write(')');
        }
        else
        {
            $this->generateExpression($expression->get_TargetObject());
        }
        
        if($expression->get_TargetObject() instanceof ParentReferenceExpression ||
            $expression->get_TargetObject() instanceof SelfReferenceExpression)
        {
            $this->output->write('::');
        }
        else
        {
            $this->output->write('->');
        }
        
        $this->outputIdentifier($expression->get_MethodName());
        
    }
    
    private function generateMethodReturnStatement(MethodReturnStatement $statement)
    {
        $this->output->write('return');
        if($statement->Expression != null)
        {
            $this->output->write(' ');
            $this->generateExpression($statement->Expression);
        }
        $this->output->write(';');
    }
    
    private function generateMethods(TypeDeclaration $declaration)
    {
        foreach($declaration->Members as $member)
        {
            if($member instanceof Method)
            {
                //$this->currentMember = $member;
                
                if($this->options->get_BlankLinesBetweenMembers())
                {
                    $this->output->writeLine();
                }
                $this->generateCommentStatements($member->get_Comments());
                $this->generateMethod($member, $declaration);
            }
        }
    }
    
    private function generateNamespace(CodeNamespace $namespace)
    {
        $this->generateCommentStatements($namespace->Comments);
        $this->generateNamespaceStart($namespace);
        $this->output->writeLine();
        $this->generateStatements($namespace->get_Statements());
        $this->output->writeLine();
        $this->generateTypes($namespace);
        $this->generateNamespaceEnd($namespace);
    }
    
    private function generateNamespaceEnd(CodeNamespace $namespace)
    {
        if($namespace->Name != null && strlen($namespace->Name) > 0)
        {
            $this->output->Indent--;
            $this->output->writeLine('}');
        }
    }
    
    private function generateNamespaces(CodeUnit $compileUnit)
    {
        foreach($compileUnit->Namespaces as $namespace)
        {
            $this->generateCodeFromNamespace($namespace, $this->output, $this->options);
        }
    }
    
    private function generateNamespaceStart(CodeNamespace $namespace)
    {
        if($namespace->Name != null && strlen($namespace->Name) > 0)
        {
            $this->output->write('namespace ');
            
            $names = explode('\\', $namespace->Name);
            
            if(!$this->isKeyword($names[0]) && $this->isValidIdentifier($names[0]))
            {
                $this->output->write($names[0]);
            }
            else
            {
                throw new InvalidArgumentException(
                    "The name $names[0] is not a valid namespace name.");
            }
            
            for($i = 1; $i < count($names); $i++)
            {
                if(!$this->isKeyword($names[$i]) && $this->isValidIdentifier($names[$i]))
                {
                    $this->output->write('\\');
                    $this->output->write($names[$i]);
                }
                else
                {
                    throw new InvalidArgumentException(
                        "The name $names[0] is not a valid namespace name.");
                }
            }
            
            $this->outputStartingBrace();
            $this->output->Indent++;
        }
    }
    
    private function generateNowDocStringExpression(NowDocStringExpression $expression)
    {
        $this->output->writeLine('<<<\'' . $expression->get_Identifier() . '\'');
        
        $indent = $this->output->Indent;
        $this->output->Indent = 0;
        
        $this->output->writeLine($expression->get_Value());
        $this->output->writeLine($expression->get_Identifier());
        
        $this->output->Indent = $indent;
    }
    
    private function generateObjectCreateExpression(ObjectCreateExpression $expression)
    {
        $this->output->write('new ');
        $this->outputType($expression->get_CreateType());
        $this->output->write('(');
        $this->outputExpressionList($expression->get_Parameters(), false);
        $this->output->write(')');
    }
    
    private function generateParameterDeclaration(ParameterDeclarationExpression $expression)
    {
                if($expression->get_TypeHint() != null)
                {
                    $this->output->write($expression->get_TypeHint()->get_Name() . ' ');
                }

                if($expression->get_ByReference())
                {
                    $this->output->write('&');
                }
                
                $this->output->write('$');
                $this->outputIdentifier($expression->get_Name());

                if($expression->get_HasDefaultValue())
                {
                    $this->output->write(' = ');
                    $this->generatePrimitiveExpression($expression->get_DefaultValue());
                }
    }
    
    private function generatePrimitiveExpression(PrimitiveExpression $expression)
    {
        $value = $expression->get_Value();
        if(is_int($value) || is_float($value))
        {
            $this->output->write($value);
        }
        elseif(is_string($value))
        {
            $this->output->write("'" . $value . "'");
        }
        elseif(is_bool($value))
        {
            $this->output->write($value ? 'true' : 'false');
        }
        elseif(is_null($value))
        {
            $this->output->write('null');
        }
    }
    
    private function generateSingleQuotedStringExpression(
        SingleQuotedStringExpression $expression)
    {
        $this->output->write('\'');
        $this->output->write($expression->get_Value());
        $this->output->write('\'');
    }
    
    private function generateSnippetExpression (SnippetExpression $expression)
    {
        $this->output->write($expression->get_Value());
    }
    
    private function generateSnippetStatement(SnippetStatement $statement)
    {
        $this->output->write($statement->get_value());
        if(!$this->generatingForLoop)
        {
            $this->output->writeLine(';');
        }
    }
    
    private function generateStatement(Statement $statement)
    {
        $this->generateCommentStatements($statement->Comments);
        
        if($statement instanceof CommentStatement)
        {
            $this->generateCommentStatement($statement);
        }
        elseif($statement instanceof MethodReturnStatement)
        {
            $this->generateMethodReturnStatement($statement);
        }
        elseif($statement instanceof ConditionStatement)
        {
            $this->generateConditionStatement($statement);
        }
        elseif($statement instanceof TryCatchStatement)
        {
            $this->generateTryCatchStatement($statement);
        }
        elseif($statement instanceof AssignStatement)
        {
            $this->generateAssignStatement($statement);
        }
        elseif($statement instanceof ExpressionStatement)
        {
            $this->generateExpressionStatement($statement);
        }
        elseif($statement instanceof IterationStatement)
        {
            $this->generateIterationStatement($statement);
        }
        elseif($statement instanceof ThrowStatement)
        {
            $this->generateThrowStatement($statement);
        }
        elseif($statement instanceof SnippetStatement)
        {
            $indent = $this->output->Indent;
            $this->output->Indent = 0;
            $this->generateSnippetStatement($statement);
            $this->output->Indent = $indent;
        }
        elseif($statement instanceof CodeFunction)
        {
            $this->generateFunction($statement);
        }
        elseif($statement instanceof EchoStatement)
        {
            $this->generateEchoStatement($statement);
        }
    }
    
    private function generateStatements(StatementCollection $statements)
    {
        foreach($statements as $statement)
        {
            $this->generateCodeFromStatement($statement, $this->output, $this->options);
            
            for($i = 0; $i < $statement->get_BlankLinesAfter(); $i++)
            {
                $this->output->writeLine();
            }
        }
    }
    
    private function generateThisExpression(ThisReferenceExpression $expression)
    {
        $this->output->write('$this');
    }
    
    private function generateThrowStatement(ThrowStatement $statement)
    {
        $this->output->write('throw ');
        $this->generateExpression($statement->get_ToThrow());
        $this->output->writeLine(';');
    }
    
    private function generateTryCatchStatement(TryCatchStatement $statement)
    {
        $this->output->write('try');
        $this->outputStartingBrace();
        $this->output->Indent++;
        $this->generateStatements($statement->get_TryStatements());
        $this->output->Indent--;
        
        foreach($statement->get_CatchClauses() as $catchClause)
        {
            $this->output->write('}');
            if($this->options->get_ElseOnClosing())
            {
                $this->output->write(' ');
            }
            else
            {
                $this->output->writeLine();
            }
            $this->output->write('catch (');
            $this->outputType($catchClause->get_CatchExceptionType());
            $this->output->write(' ');
            $this->outputIdentifier($catchClause->get_LocalName());
            $this->output->write(')');
            $this->outputStartingBrace();
            $this->output->Indent++;
            $this->generateStatements($catchClause->get_Statements());
            $this->output->Indent--;
        }
        
        $this->output->writeLine('}');
    }
    
    private function generateType(TypeDeclaration $typeDeclaration)
    {
        $this->currentClass = $typeDeclaration;
        $this->generateCommentStatements($typeDeclaration->Comments);
        $this->generateTypeStart($typeDeclaration);
        
        if($this->options->get_verbatimOrder())
        {
            foreach($typeDeclaration->Members as $member)
            {
                $this->generateTypeMember($member, $typeDeclaration);
            }
        }
        else
        {
            $this->generateMethods($typeDeclaration);            
            $this->generateFields($typeDeclaration);

        }
        
        $this->currentClass = $typeDeclaration;
        $this->generateTypeEnd($typeDeclaration);
        
    }
    
    private function generateTypeEnd(TypeDeclaration $typeDeclaration)
    {
        $this->output->Indent--;
        $this->output->writeLine('}');
    }
    
    private function generateTypeMember(
        TypeMember $member, TypeDeclaration $declaration)
    {
        if($this->options->get_BlankLinesBetweenMembers())
        {
            $this->output->writeLine();
        }
        
        $this->generateCommentStatements($member->Comments);
        
        if($member instanceof Field)
        {
            $this->generateField($member);
        }
        elseif($member instanceof Method)
        {
            $this->generateMethod($member, $declaration);
        }
    }

    private function generateTypes(CodeNamespace $namespace)
    {
        foreach($namespace->Types as $type)
        {
            if($this->options->get_BlankLinesBetweenMembers())
            {
                $this->output->writeLine();
            }

            $this->generateCodeFromType($type, $this->output, $this->options);
        }
    }
    
    private function generateTypeStart(TypeDeclaration $codeType)
    {
        if($this->isCurrentInterface())
        {
            $this->output->write('interface ');
        }
        elseif($this->isCurrentClass())
        {
            $this->output->write('class ');
        }
        
        $this->output->write($codeType->get_name() . ' ');
        
        $baseClassIndex = null;
        $baseClassCount = 0;
        
        foreach($codeType->BaseTypes as $baseType)
        {
            if($baseType->get_isClass())
            {
                $baseClassIndex = $codeType->BaseTypes->key();
                $baseClassCount++;
            }
            
            if($baseClassCount > 1)
            {
                throw new UnexpectedValueException(
                    "Only one base class allowed.");
            }
        }
        
        if($baseClassCount == 1)
        {
            $this->output->write('extends ' . 
                $codeType->BaseTypes[$baseClassIndex]->get_name() . ' ');
        }
        
        $isFirstInterface = true;
        
        if(count($codeType->BaseTypes) - $baseClassCount > 0)
        {
            foreach($codeType->BaseTypes as $baseType)
            {
                if($baseType->get_isInterface())
                {
                    $this->output->write($isFirstInterface ? 'implements ' : ', ');
                    $isFirstInterface = false;
                    $this->output->write($baseType->get_Name());
                }
            }
        }
        
        $this->outputStartingBrace();
        $this->output->Indent++;
    }
    
    private function generateUnaryOperatorExpression(UnaryOperatorExpression $expression)
    {
        switch ($expression->get_Operator())
        {
            case UnaryOperator::ARITHMETIC_NEGATION :
            case UnaryOperator::BITWISE_NOT :
            case UnaryOperator::ERROR_CONTROL :
            case UnaryOperator::LOGICAL_NEGATION :
            case UnaryOperator::PRE_DECREMENT :
            case UnaryOperator::PRE_INCREMENT :

                $this->output->write($expression->get_Operator());
                $this->generateExpression($expression->get_Expression());
                break;

            case UnaryOperator::POST_DECREMENT :
            case UnaryOperator::POST_INCREMENT :
                
                $this->generateExpression($expression->get_Expression());
                $this->output->write($expression->get_Operator());
                break;
        }
    }
    
    private function generateVariableReferenceExpression(VariableReferenceExpression $expression)
    {
        $this->output->write('$');
        $this->outputIdentifier($expression->get_VariableName());
    }
    
    private function outputExpressionList(
        ExpressionCollection $expressions, $newLineBetweenItems)
    {
        $isFirst = true;
        foreach($expressions as $expression)
        {
            if($isFirst)
            {
                $isFirst = false;
            }
            else
            {
                $this->output->write(', ');
            }
            $this->generateCodeFromExpression($expression, $this->output, $this->options);
        }
    }
    
    private function outputIdentifier($name)
    {
        $this->output->write($this->createEscapedIdentifier($name));
    }
    
    private function outputBinaryOperator($operator)
    {
        if(BinaryOperator::Defines($operator))
        {
            $this->output->write($operator);
        }
        else
        {
            throw new InvalidArgumentException(
                "Operator: $operator not supported.");
        }
    }
    
    private function outputParameters(ParameterDeclarationExpressionCollection $parameters)
    {
        $this->output->write(' (');
        
        if($parameters->count() > 0)
        {        
            $isFirstParameter = true;

            foreach($parameters as $parameter)
            {
                if($isFirstParameter)
                {
                    $isFirstParameter = false;
                }
                else
                {
                    $this->output->write(', ');
                }
                $this->generateParameterDeclaration($parameter);
            }
        }
        
        $this->output->write(')');
    }
    
    private function outputStartingBrace()
    {
        if($this->options->get_BracingStyle() == BracingStyle::Allman)
        {
            $this->output->writeLine();
            $this->output->writeLine('{');
        }
        elseif($this->options->get_BracingStyle() == BracingStyle::KR)
        {
            $this->output->writeLine('{');
        }
    }
    
    private function outputType(TypeReference $codeType)
    {
        $this->output->write($codeType->get_name());
    }
    
    private function continueOnNewLine($value)
    {
        $this->output->writeLine($value);
    }
    
    private static function isKeyword($value)
    {
        return in_array($value, self::$keywords);
    }
    
    public $EscapeChar = '_';
    
    private $currentClass;
    private $inNestedBinary = false;
    private $generatingForLoop = false;
    private $options;
    private $output;
    
    private static $keywords = array(
        'abstract', 'and', 'array', 'as', 
        'break',
        'case', 'catch', 'cfunction', 'class', 'clone', 'const', 'continue', 
        '__CLASS__', '__COMPILER_HALT_OFFSET__',        
        'declare', 'default', 'do', '__DIR__', 'die', 'DEFAULT_INCLUDE_PATH', 
        'else', 'elseif', 'enddeclare', 'endfor', 'endforeach', 'endif', 'echo',
        'endswitch', 'endwhile', 'extends', 'empty', 'exit', 'eval', 'E_ERROR',
        'E_WARNING', 'E_PARSE', 'E_NOTICE', 'E_CORE_ERROR', 'E_CORE_WARNING',
        'E_COMPILE_ERROR', 'E_COMPILE_WARNING', 'E_USER_ERROR', 'E_USER_WARNING',
        'E_USER_NOTICE', 'E_DEPRECATED', 'E_USER_DEPRECATED', 'E_ALL', 'E_STRICT',
        'final', 'for', 'foreach', 'function', '__FILE__', '__FUNCTION__', 'FALSE', 'false',
        'global', 'goto', 
        '__halt_compiler',
        'if', 'implements', 'interface', 'instanceof', 'include', 'include_once', 'isset', 
        '__LINE__', 'list',
        '__METHOD__',
        'namespace', 'new', '__NAMESPACE__', 'null', 'NULL',
        'old_function', 'or', 
        'private', 'protected', 'public', 'print', 'parent', 'PHP_VERSION',
        'PHP_MAJOR_VERSION', 'PHP_MINOR_VERSION', 'PHP_RELEASE_VERSION',
        'PHP_VERSION_ID', 'PHP_EXTRA_VERSION', 'PHP_ZTS', 'PHP_DEBUG', 
        'PHP_MAXPATHLEN', 'PHP_OS', 'PHP_SAPI', 'PHP_EOL', 'PHP_INT_MAX',
        'PHP_INT_SIZE', 'PEAR_INSTALL_DIR', 'PEAR_EXTENSION_DIR', 'PHP_EXTENSION_DIR',
        'PHP_PREFIX', 'PHP_BINDIR', 'PHP_BINARY', 'PHP_MANDIR', 'PHP_LIBDIR',
        'PHP_DATADIR', 'PHP_SYSCONFDIR', 'PHP_LOCALSTATEDIR', 'PHP_CONFIG_FILE_PATH',
        'PHP_CONFIG_FILE_SCAN_DIR', 'PHP_SHLIB_SUFIX', 'PHP_OUTPUT_HANDLER_START',
        'PHP_OUTPUT_HANDLER_CONT', 'PHP_OUTPUT_HANDLER_END',
        'require', 'require_once', 'return',
        'static', 'switch', 'self',
        'throw', 'try', 'TRUE', 'true',
        'use', 'unset',
        'var',
        'while',
        'xor');
}

?>
